package com.xrui.hbase.tools;

import com.moz.kiji.delegation.Lookups;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.util.GenericOptionsParser;

import java.util.Arrays;

/**
 * Main entry point to launch HBase tools.
 *
 * <p>All tools launched through <tt>bin/hbase &lt;tool&gt;</tt> are detected and
 * instantiated through this module. Tools should implement the {@link HBaseTool}
 * interface. In addition, each tool must advertise itself by adding a line to
 * a resource file contained in its jar at
 * <tt>META-INF/services/com.tr.ts.HBaseTool</tt>.
 * This path can be added to your build by providing it under <tt>src/main/resources</tt>.</p>
 */
public final class HBaseToolLauncher extends Configured {

  /**
   * Programmatic entry point to the tool launcher. Locates a tool to run
   * based on the name provided as the first argument, then invokes it.
   * Hadoop property-based arguments will be parsed by HBaseToolLauncher.run()
   * in a manner similar to Hadoop's ToolRunner.
   *
   * @param args The command-line arguments. The first one should be the
   *     name of the tool to run.
   * @throws Exception If there is an error.
   * @return 0 on program success, non-zero on error.
   */
  public int run(String[] args) throws Exception {
    if (args.length == 0) {
      System.err.println("Error: Must run 'hbase <toolName>'.");
      System.err.println("Try running 'hbase help' to see the available tools.");
      return 1;
    }

    String toolName = args[0];
    String[] nonToolNameArgs = Arrays.copyOfRange(args, 1, args.length);

    HBaseTool tool = getToolForName(toolName);

    if (null == tool) {
      System.err.println("No tool available with name [" + toolName + "]");
      System.err.println("Try running 'hbase help' to see the available tools.");

      return 1;
    }

    // Continue on to Hadoop property argument parsing and tool launch.
    return run(tool, nonToolNameArgs);
  }

  /**
   * Return the tool specified by the 'toolName' argument.
   * (package-protected for use by the HelpTool, and for testing.)
   *
   * @param toolName the name of the tool to instantiate.
   * @return the HBaseTool that provides for that name, or null if none does.
   */
  HBaseTool getToolForName(String toolName) {
    HBaseTool tool = null;

    // Iterate over available tools, searching for the one with
    // the same name as the supplied tool name argument.
    for (HBaseTool candidate : Lookups.get(HBaseTool.class)) {
      if (toolName.equals(candidate.getName())) {
        tool = candidate;
        break;
      }
    }

    return tool;
  }

  /**
   * Programmatic entry point to the tool launcher if a tool is already selected.
   * Hadoop property-based arguments will be parsed by HBaseToolLauncher.run()
   * in a manner similar to Hadoop's ToolRunner.
   *
   * @param tool The HBaseTool to run.
   * @param args The command-line arguments, excluding the name of the tool to run.
   * @throws Exception If there is an error.
   * @return 0 on program success, non-zero on error.
   */
  public int run(HBaseTool tool, String[] args) throws Exception {
    Configuration conf = getConf();
    if (conf == null) {
      conf = new Configuration();
      setConf(conf);
    }

    // Mimic behavior of Hadoop's ToolRunner.run().
    GenericOptionsParser parser = new GenericOptionsParser(conf, args);
    conf = HBaseConfiguration.addHbaseResources(conf);

    tool.setConf(conf);

    // Get remaining arguments and invoke the tool with them.
    String[] toolArgs = parser.getRemainingArgs();

    return tool.toolMain(Arrays.asList(toolArgs));
  }

  /**
   * Program entry point. This method does not return; it calls System.exit() with the
   * return code from the called tool.
   *
   * @param args The command-line arguments, starting with the name of the tool to run.
   * @throws Exception If there is an error.
   */
  public static void main(String[] args) throws Exception {
    System.exit(new HBaseToolLauncher().run(args));
  }
}
